#pragma once

// WARNING: Logger is NOT data-loss protected if number of messages reaches ERRBUF_WIDTH-1!
// 			If the ERRBUF_WIDTH is too small, the logger pthread is too slow or
//			 the number of messages is too big last messages are overwrited,
//			 see errlog()'s comments.
// WARNING: The maximum length of a message is predefined as ERRBUF_DEPTH.

#include <pthread.h>
#include <stdarg.h>

#define LOG_NOERR 0
#define LOG_WARNG 1
#define LOG_ERROR 2
#define LOG_FATAL 3
#define LOG_PANIC 4
#define LOG_TEROR 5
// Feel free to add more :)

int LOG_LEVEL = -1;
int LOG_ABORT = 0;

#define LOG_FILE_PATH "./log"
FILE * LOG_FILE;

pthread_t logger_thread;

// WARNING: ERRBUF_WIDTH must be 2^N, N>0
#define ERRBUF_WIDTH 1024
#define ERRBUF_DEPTH 1024

char** ERRBUF = NULL;
unsigned int ERRBUF_TAIL = 0;
unsigned int ERRBUF_HEAD = 0;

void * errlogger(void * null = NULL)
{
	while(1)
	{
		// Exit condition
		if(LOG_ABORT!=0 && (ERRBUF_TAIL % ERRBUF_WIDTH) == (ERRBUF_HEAD % ERRBUF_WIDTH))
			break;
		// This is a self-lock which resolves itself after some time.
		// Since this thread has nothing to anyway, it doesn't matter.
		if (!(ERRBUF[ERRBUF_TAIL]))
			continue;
		fprintf(LOG_FILE,"%s",ERRBUF[ERRBUF_TAIL]);
		free(ERRBUF[ERRBUF_TAIL]);
		ERRBUF[ERRBUF_TAIL] = NULL;
		ERRBUF_TAIL = (ERRBUF_TAIL+1) % ERRBUF_WIDTH;
	}

	LOG_LEVEL = -1;
	fclose(LOG_FILE);
	LOG_FILE = NULL;
	free(ERRBUF);
	ERRBUF = NULL;
	ERRBUF_TAIL = ERRBUF_HEAD = 0;
	
	pthread_exit(NULL);
}

void errlog_deinit()
{
	LOG_ABORT = 1;
	void* buf;
	pthread_join(logger_thread,&buf);
	LOG_ABORT = 0;
}

void errlog_init(int level)
{
	if(LOG_LEVEL != -1)
		// Was already called, better to abort now.
		exit(-1);
	
	LOG_LEVEL = level;
	
	LOG_FILE = fopen(LOG_FILE_PATH, "w");
	if(!LOG_FILE)
		exit(-1);

	ERRBUF = (char**) malloc(sizeof(char*) * ERRBUF_WIDTH);
	if (!ERRBUF)
		exit(-1);
	int i;
	for(i=0; i<ERRBUF_WIDTH; i++)
		ERRBUF[i] = NULL;

	if(pthread_create(&logger_thread, NULL, errlogger, NULL) != 0)
		exit(-1);
}

// TODO: Maybe this function should be inlined.
int errlog(int level, const char* format, ...)
{
	if(level<LOG_LEVEL)
		return 0;
	
	char* s = (char*) malloc(sizeof(char) * ERRBUF_DEPTH);
	va_list args;
	va_start(args, format);
	int r = vsprintf(s ,format, args);
	va_end(args);

	unsigned int h = __sync_fetch_and_add (&ERRBUF_HEAD, 1) % ERRBUF_WIDTH;
	unsigned int t = ERRBUF_TAIL % ERRBUF_WIDTH;
	if(h == t-1 || (h == ERRBUF_WIDTH-1 && t == 0))
		// The maximum number of messages has been reached.
		exit(-1);
	
	ERRBUF[h] = s;
	return r;
}
